/*
 * Copyright 2007-2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.paoding.rose;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

import net.paoding.rose.impl.scanning.ModuleScanner;
import net.paoding.rose.impl.scanning.ModuleScannerImpl;
import net.paoding.rose.impl.util.SpringUtils;
import net.paoding.rose.web.impl.context.ContextLoader;
import net.paoding.rose.web.impl.module.Module;
import net.paoding.rose.web.impl.module.ModulesBuilder;
import net.paoding.rose.web.impl.module.NamedControllerInterceptor;
import net.paoding.rose.web.impl.thread.Path;
import net.paoding.rose.web.impl.thread.UserProfile;
import net.paoding.rose.web.var.PrivateVar;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.web.filter.GenericFilterBean;

/**
 * Paoding Rose是一个MVC开发框架(这里的MVC概念更接近于Grails/RoR的MVC，而非传统的Java
 * MVC，前者范畴较广)。使用Rose框架开发，除了将<strong>paoding-rose.jar</strong>
 * 以及依赖的jar拷贝到classpath下，剩下的最大一项工作就是配置{@link RoseFilter}。
 * <p>
 * 您，作为Rose的使用者或评估者，您只需要按照下面介绍的方式将 {@link RoseFilter}
 * 拷贝配置在web.xml中，用以拦截<strong>所有</strong>的web请求即可。虽然{@link RoseFilter}
 * 会拦截所有请求，但是Rose能够判断所拦截的请求是否应该由对应的Controller控制器来处理(详见Referentce),
 * 如果Rose判断该请求不应在rose处理范围内，则 {@link RoseFilter}会让他“通过”不做任何处理。
 * <p>
 * 因为Rose使用Filter而非Servlet来接收并转发请求给控制器Controller，所以这里的配置必须满足一个很重要的注意点：
 * 即Rose过滤器必须配置在所有过滤器的
 * <strong>最后</strong>。只有这样，才能保证所有应该由控制器处理的web请求都能够通过其他可能的Filter拦截。
 * <p>
 * 
 * 绝大多数情况，按以下配置即可：
 * 
 * <pre>
 * 	&lt;filter&gt;
 * 		&lt;filter-name&gt;roseFilter&lt;/filter-name&gt;
 * 		&lt;filter-class&gt;net.paoding.rose.RoseFilter&lt;/filter-class&gt;
 * 	&lt;/filter&gt;
 * 
 * 	&lt;filter-mapping&gt;
 * 		&lt;filter-name&gt;roseFilter&lt;/filter-name&gt;
 * 		&lt;url-pattern&gt;/*&lt;/url-pattern&gt;
 * 		&lt;dispatcher&gt;REQUEST&lt;/dispatcher&gt;
 * 		&lt;dispatcher&gt;FORWARD&lt;/dispatcher&gt;
 * 		&lt;dispatcher&gt;INCLUDE&lt;/dispatcher&gt;
 * 	&lt;/filter-mapping&gt;
 * 
 * </pre>
 * 
 * 再次重复一下稍微需要注意的地方：<br>
 * 1)如上所说的<strong>filter-mapping</strong>必须配置在所有Filter Mapping的最后。
 * 2)不能将以上dispatcher的forward去掉，否则forward的请求Rose框架将拦截不到。include也是如此<br>
 * <p>
 * 
 * @author qieqie.wang@paoding.net
 * 
 */
public class RoseFilter extends GenericFilterBean {

	/**
	 * 默认的applicationContext地址
	 */
	public static final String DEFAULT_CONTEXT_CONFIG_LOCATION = ""
			+ "/WEB-INF/applicationContext*.xml"
			+ ",classpath:applicationContext*.xml"
			+ ",classpath-jar:applicationContext*.xml";

	public static final String DEFAULT_USER_PROFILE = UserProfile.class
			.getName();

	// 使用的applicationContext地址，大部分情况下留空就好，按照惯例开发，Rose会处理好的
	private String contextConfigLocation;

	// Spring应用上下文环境装载对象，把它记录下来在destroy的时候对它进行close
	private ContextLoader contextLoader;

	// web模块
	private List<Module> modules = Collections
			.unmodifiableList(new ArrayList<Module>(0));

	// 所有的请求被RoseFilter过滤时，基本上都将转到该RoseEngine处理
	private RoseEngine engine;

	// userProfile负责决定和用户特有信息相关的一些决策，在控制器调用完毕，渲染页面之前调用
	private String userProfileClass = DEFAULT_USER_PROFILE;

	/**
	 * 改变默认行为，告知Rose要读取的applicationContext地址
	 */
	public void setContextConfigLocation(String contextConfigLocation) {
		this.contextConfigLocation = contextConfigLocation;
	}

	@Override
	protected final void initFilterBean() throws ServletException {
		// 赶快把servletContext放进去，以可以使用Rc类提供API
		PrivateVar.servletContext(getServletContext());

		// 如果没有通过Filter的init参数配置contextConfigLocation的话，
		// 则看看是否使用Spring要求的方式配置了contextConfigLocation没有?
		if (StringUtils.isBlank(contextConfigLocation)) {
			contextConfigLocation = Rc.getServletContext().getInitParameter(
					"contextConfigLocation");
		}

		// 如果Filter和Context都没有配置contextConfigLocation，那就使用默认配置了
		// 我们建议开发者兄弟们，其实您为什么要配置呢？除非特殊情况，否则就使用惯例好了，是吧？大家都轻松
		if (StringUtils.isBlank(contextConfigLocation)) {
			contextConfigLocation = DEFAULT_CONTEXT_CONFIG_LOCATION;
		}

		try {
			// 先识别并初始化中间层、资源层(数据库连接、其他资源连接等等)：
			// 如果applicationContext已经被其他程序初始化过的话，Rose自动使用它，否则Rose自行初始化它
			if (Rc.getRootWebApplicationContext() == null) {
				contextLoader = new ContextLoader(contextConfigLocation);
				contextLoader.initWebApplicationContext(getServletContext());
			}

			// 然后自动扫描识别并初始化Controller控制器:
			// controller类应该放到package为xxx.controllers或其直接子package下(
			// 并以Controller结尾或标注@net.paoding.rose.web.annotation.Controller)
			// 按照这样的约定，Rose就能自动“挑”出他们出来。
			ModuleScanner moduleScanner = (ModuleScanner) SpringUtils.getBean(
					Rc.getRootWebApplicationContext(), ModuleScanner.class);
			if (moduleScanner == null) {
				moduleScanner = (ModuleScanner) SpringUtils
						.createBean(ModuleScannerImpl.class);
			}
			modules = new ModulesBuilder().build(Rc
					.getRootWebApplicationContext(), moduleScanner);
			engine = new RoseEngine(modules);
			engine.setUserProfile((UserProfile) BeanUtils
					.instantiateClass(Class.forName(userProfileClass)));
			if (logger.isInfoEnabled()) {
				StringBuilder sb = new StringBuilder(4096);
				dumpModules(modules, sb);
				logger.info(sb.toString());
			}
			// 控制台提示
			System.out.println("Rose Initiated (version=0.9)");
			logger.info("Rose Initiated (version=0.9)");
		} catch (Exception e) {
			throw new ServletException(e);
		}
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain filterChain) throws IOException, ServletException {
		// warpper
		HttpServletRequest httpRequest = new HttpServletRequestWrapper(
				(HttpServletRequest) request);
		HttpServletResponse httpResponse = new HttpServletResponseWrapper(
				(HttpServletResponse) response);

		// debug
		if (logger.isDebugEnabled()) {
			logger.debug(httpRequest.getRequestURL());
		}

		// set to context
		PrivateVar.request(httpRequest);
		PrivateVar.response(httpResponse);

		// 调用engine
		Object instruction = engine.invoke((HttpServletRequest) httpRequest,
				(HttpServletResponse) httpResponse);

		// 返回的instruction如果是RoseEngine.CONTINUE串，表示不是rose请求，应该传递出去
		if (RoseConstants.CONTINUE.equals(instruction)) {
			if (logger.isDebugEnabled()) {
				Path path = PrivateVar.invocationVariables().getPath();
				String uri = path != null ? path.getUri() : httpRequest
						.getRequestURI();
				logger.debug("not rose uri: " + uri);
			}
			// 调用其它Filter
			filterChain.doFilter(httpRequest, httpResponse);
		}
	}

	@Override
	public void destroy() {
		for (Module module : modules) {
			Rc.getServletContext().log(
					"Closing Spring Module WebApplicationContext: "
							+ module.getPath());
			Rc.getWebApplicationContext(module.getPath()).close();
		}
		if (contextLoader != null) {
			contextLoader.closeWebApplicationContext(getServletContext());
		}
		super.destroy();
	}
	
	
	//----------

	private void dumpModules(List<Module> modules, StringBuilder sb) {
		sb.append("\n--------Dump Modules(Total ").append(modules.size()).append(")--------");
		sb.append("\n");
		for (int i = 0; i < modules.size(); i++) {
			Module module = modules.get(i);
			sb.append("module ").append(i + 1).append(":\n");
			sb.append("\tpath='").append(module.getPath());
			sb.append("';\n\tcontrollers=[");
			List<String> controllerPaths = module.getControllerPaths();
			for (int j = 0; j < controllerPaths.size(); j++) {
				String controllerPath = controllerPaths.get(j);
				if (j != 0) {
					sb.append(", ");
				}
				sb.append("'").append(controllerPath).append("'");
			}
			sb.append("];\n\tinterceptors=[");
			NamedControllerInterceptor[] interceptors = module.getInterceptors();
			for (int j = 0; j < interceptors.length; j++) {
				if (j != 0) {
					sb.append(", ");
				}
				sb.append(interceptors[j].getName())
					.append("{").append(interceptors[j].getPriority()).append("}");
			}
			sb.append("];\n\terrorHander=").append(module.getErrorHandler());
			sb.append(";\n\tdefaultControllerPath=").append(module.getDefaultControllerPath());
			sb.append("\n\n");
		}
		sb.append("--------end of dump--------");
	}

}
